/*******************************************************************************
 * Copyright 2007-2008 Vidar Svansson
 *
 * This file is part of JAdapter.
 *
 * JAdapter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JAdapter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JAdapter.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/

package org.jadapter.func;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;



/**
 * Created by IntelliJ IDEA.
 * User: vs
 * Date: 25.3.2008
 * Time: 15:51:20
 */
public abstract class Function<R,A> implements Lambda<R,A>{

    public R x(A a){ return invoke(a); }

    public abstract R invoke(A arg);

    public static <T> void remove(Lambda<Boolean,T> function, Collection<T> c) {
        for (Iterator<T> it = c.iterator(); it.hasNext();)
            if (!function.x(it.next())) it.remove();
    }
    
    public <T> Collection<T> filter(Lambda<Boolean,T> function, 
            Collection<T> collection) {
        return filter(function, collection, new LinkedList<T>());
    }  
    
    public <T> Collection<T> filter(Lambda<Boolean,T> function, 
            Collection<T> collection, Collection<T> result) {
       
        for (Iterator<T> it = collection.iterator(); it.hasNext();){
            T t = it.next();
            if(function.x(t)) result.add(t);         
        } return result;
    }      

    public static <T> T find(Lambda<Boolean,T> function, Collection<T> c) {
        for(T t : c) if (function.x(t)) return t;
        return null;
    }
    
    public static <R,A> Collection<R> map(Lambda<R,A> function, Collection<A> sequence) {        
        return map(function, sequence, new ArrayList<R>(sequence.size()));
    }
    
    public static <R,A> Collection<R> map(Lambda<R,A> function, Collection<A> collection, Collection<R> result) {      
        for(A a : collection) result.add(function.x(a));               
        return result;        
    }    

    public static <T> T reduce(Lambda<Lambda<T,T>,T> function, 
            List<T> sequence, T initial) {
        return reduce(expand(function), sequence, initial);
    }   

    public static <T> T reduce(Function<Function<T,T>,T> function, 
            List<T> sequence, T initial) {
        return reduce(expand(function), sequence, initial);
    }     
    
    public static <T> T reduce(Function.F2<T, T, T> function, List<T> sequence, T initial) {
        T result = initial;
        for (T arg : sequence) result = function.invoke(result, arg) ;
        return result;
    }
    
      
    public static <RET,A0, A1> Function.F2<RET, A0, A1> expand(
            final Function<Function<RET,A1>,A0> function){
        return new Function.F2<RET, A0, A1>(){
            @Override
			public RET invoke(A0 a0, A1 a1) {
                return function.x(a0).x(a1);
            }
            
        };      
    }    
    
    public static <RET,A0, A1> Function.F2<RET, A0, A1> expand(
            final Lambda<Lambda<RET,A1>,A0> function){
        return new Function.F2<RET, A0, A1>(){
            @Override
			public RET invoke(A0 a0, A1 a1) {
                return function.x(a0).x(a1);
            }
            
        };      
    }

    
    public static <R,A0,A1> Lambda<R,A1> curry(
            final Lambda<Lambda<R,A1>,A0> f, 
            final A0 a0) {
        
        return new Lambda<R,A1>(){
            public R x(A1 a1) {
                return f.x(a0).x(a1);
            }           
        };
    }   

    public static <RET,ARG0,ARG1> Lambda<RET,ARG1> curry(
            final Function<Function<RET,ARG1>,ARG0> f, 
            final ARG0 a){

        return new Lambda<RET,ARG1>(){
            public RET x(ARG1 arg) {
                return f.x(a).x(arg);
            }
        };
    }  

    public static abstract class F2<RET,A0,A1> 
            implements Lambda<Lambda<RET,A1>,A0> {     
        
        private final F2<RET,A0,A1> self;

        public F2(){ self = this; }

        public final Lambda<RET, A1> x(final A0 a0) {
            return new Lambda<RET,A1>(){
                public RET x(A1 a1) {
                    return y(a0,a1);
                }           

                private RET y(A0 a0, A1 a1){
                    return self.invoke(a0, a1);
                }
            };
        }
        
        public RET x(A0 a0, A1 a1){ return invoke(a0, a1); }

        public abstract RET invoke(A0 a0, A1 a1);   

    }     

    public static abstract class F3<RET,A0,A1,A2> 
            implements Lambda<Lambda<Lambda<RET,A2>,A1>,A0>{
        
        private final F3<RET,A0,A1,A2> self;

        public F3(){ self = this; }

        public final Lambda<Lambda<RET, A2>, A1> x(final A0 a0) {
            return new F2<RET,A1,A2>(){
                @Override
				public RET invoke(A1 a1, A2 a2) {
                    return y(a0, a1, a2);
                }          
                private RET y(A0 a0, A1 a1, A2 a2){
                    return self.invoke(a0, a1, a2);
                }
            };
        }
        
        public RET x(A0 a0, A1 a1, A2 a2){ return invoke(a0, a1, a2); }        

        public abstract RET invoke(A0 a0, A1 a1, A2 a2);

    }

}